# 本文读写的文件，都是指 文本文件 ， 至于 视频、图片等类型的文件，它们有各自的不同的文件数据格式。
# 在python语言中，我们要读写文本文件， 首先通过内置函数 open 打开一个文件。
# open 函数会返回一个对象，我们可以称之为文件对象。
# 这个返回的文件对象就包含读取文本内容和写入文本内容的方法。

# 1.文本模式
# 通常，对文本文件，都是以文本模式打开。
# 文本模式打开文件后，我们的程序读取到的内容都是字符串对象，写入文件时传入的也是字符串对象。

# 1.1.open 函数的参数
# 要读写文件，首先要通过内置函数 open 打开文件，获得文件对象。
# 函数open的参数如下：
# open(
#     file,  # file 参数指定了要打开文件的路径，可以是相对路径，比如 ‘log.txt’，也可以是绝对路径，比如 ’d:\project\log\log.txt'
#     mode='r',  # mode参数指定了文件打开的 模式 ，常用的打开模式有：r 只读模式，这是最常用的一种模式、 w 只写模式、a 追加模式。
#     buffering=-1,
#     encoding=None,  # encoding 参数指定了读写文本文件时，使用的 字符编解码 方式。
#     errors=None,
#     newline=None,
#     closefd=True,
#     opener=None
#     )

# 1.2.写文件示例
# 指定编码方式为 utf8
f = open('asset/tmp.txt', 'w', encoding='utf8')
# write方法会将字符串编码为utf8字节串写入文件
f.write('祝大家好运\n')
# 文件操作完毕后， 使用close 方法关闭该文件对象
f.close()

# 1.3.追加文件示例
# a 表示 追加模式 打开文件
f = open('asset/tmp.txt', 'a', encoding='utf8')
f.write('再次祝大家好运')
f.close()

# 1.4.读文件示例
# 指定编码方式为 gbk，gbk 编码兼容 gb2312
f = open('asset/tmp.txt', 'r', encoding='utf8')
# read 方法会在读取文件中的原始字节串后， 根据上面指定的gbk解码为字符串对象返回
content = f.read()
# 文件操作完毕后， 使用close 方法关闭该文件对象
f.close()

print(content)

# 1.5.注意点
# read 函数有参数 size，读取文本文件的时候，用来指定这次读取多少个字符。 如果不传入该参数，就是读取文件中所有的内容。
# 因为是读取文本文件的模式， 可以无须指定 mode参数
# 因为都是 英文字符，基本上所以的编码方式都兼容ASCII，可以无须指定encoding参数
f = open('asset/tmp.txt', 'r', encoding='utf8')

tmp = f.read(3)  # read 方法读取3个字符
print(tmp)  # 返回3个字符的字符串 '祝大家'

tmp = f.read(3)  # 继续使用 read 方法读取3个字符
print(tmp)  # 返回3个字符的字符串 '好运\n'  换行符也是一个字符

tmp = f.read()  # 不加参数，读取剩余的所有字符
print(tmp)  # 返回剩余字符的字符串 '再次祝大家好运'

# 文件操作完毕后， 使用close 方法关闭该文件对象
f.close()

# 读取文本文件内容的时候，通常还会使用 readlines 方法，该方法会返回一个列表。 列表中的每个元素依次对应文本文件中每行内容。
f = open('asset/tmp.txt', 'r', encoding='utf8')
lineList = f.readlines()
f.close()
for line in lineList:
    print(line)

# 但是这种方法,列表的每个元素对应的字符串 最后有一个换行符。 如果你不想要换行符，可以使用字符串对象的 splitlines 方法
f = open('asset/tmp.txt', 'r', encoding='utf8')
content1 = f.read()  # 读取全部文件内容
f.close()
# 将文件内容字符串 按换行符 切割 到列表中，每个元素依次对应一行
lineList1 = content1.splitlines()
for line in lineList1:
    print(line)

# 2.二进制（字节）模式
# 就文件存储的底层来说，不管什么类型的文件（文本、视频、图片、word、excel等），存储的都是字节，不存在文本和二进制的区别，可以说都是二进制。
# 读写文件底层操作读写的 都是字节。
# 以文本模式打开文件后， 后面的读写文件的方法（比如 read，write等），底层实现都会自动的进行 字符串（对应Python的string对象）和字节串（对应Python的bytes对象） 的转换。

# 2.1.使用 utf8 编码写入文件，使用 二进制 读的方式打开文件，获取到 字节串对象 bytes
# 例如：tmp.txt 文件中的内容虽然是用 utf8 写的，但是通过指定 open 函数的 mode 参数为 rb，用二进制读的方式打开文件，
# 可直接读取原始的 二进制 字节串 到一个 bytes 对象 content2 中
f = open('asset/tmp.txt', 'rb')
content2 = f.read()
f.close()

# 由于是 二进制方式打开，所以得到的 content 是 字节串对象 bytes
# 内容为 b'\xe7\xa5\x9d\xe5\xa4\xa7\xe5\xae\xb6\xe5\xa5\xbd\xe8\xbf\x90\r\n\xe5\x86\x8d\xe6\xac\xa1\xe7\xa5\x9d\xe5\xa4\xa7\xe5\xae\xb6\xe5\xa5\xbd\xe8\xbf\x90'
print(content2)

# 该对象的长度是字节串里面的字节个数，就是38，每3个字节对应一个汉字的 utf8 编码
print(len(content2))

# 2.2. 二进制 方式读和写
# 2.2.1.使用 二进制 方式写入文件
# 以二进制方式写数据到文件中，传给write方法的参数不能是字符串，只能是bytes对象
# mode 参数指定为 wb 就是用二进制写的方式打开文件
f = open('asset/tmp1.txt', 'wb')
content3 = '邱倩'
# 二进制打开的文件， 写入的参数必须是bytes类型，
# 字符串对象需要调用encode进行相应的编码为bytes类型
f.write(content3.encode('utf8'))
f.close()

# 2.2.2.使用 二进制 读的方式打开文件，获取到 字节串对象 bytes
# mode 参数指定为 rb 就是用二进制读的方式打开文件
f = open('asset/tmp1.txt', 'rb')
content4 = f.read()
f.close()
print(content4)

# 3.with 语句
# 如果我们开发的程序 在进行文件读写之后，忘记使用close方法关闭文件， 就可能造成意想不到的问题。
# 我们可以使用 with 语句 打开文件，像这样，就不需要我们调用close方法关闭文件。 Python解释器会帮我们调用文件对象的close方法。
# open返回的对象 赋值为 变量 f
with open('asset/tmp.txt', 'r', encoding='utf8') as f:
    lineList2 = f.readlines()
    for line in lineList2:
        print(line)

# 4.写入缓冲
# 执行下面的代码
f = open('asset/tmp3.txt', 'w', encoding='utf8')
f.write('哈哈哈哈')
# 等待 30秒，再close文件
import time
time.sleep(30)
f.close()

# 执行该程序时，执行完写入文件内容后，会等待30秒，再关闭文件对象。
# 在这30秒还没有结束的时候，如果你打开 tmp3.txt， 将会惊奇的发现，该文件中啥内容也没有！！！
# 原因是因为执行 write 方法写入字节到文件中的时候，其实只是把这个请求提交给 操作系统。
# 操作系统为了提高效率，通常并不会立即把内容写到存储文件中， 而是写入内存的一个 缓冲区 。
# 等缓冲区的内容堆满之后，或者程序调用 close 关闭文件对象的时候，再写入到文件中。
# 如果你确实希望，在调用 write 之后，立即把内容写到文件里面，可以使用 文件对象的 flush方法。
# 如下所示：
f = open('asset/tmp3.txt', 'w', encoding='utf8')
f.write('嘿嘿嘿')
# 立即把内容写到文件里面
f.flush()
# 等待 30秒，再close文件
import time
time.sleep(30)
f.close()
# 这样再执行程序，在等待的30秒期间，你打开文件，发现里面已经有写入的字符串 “嘿嘿嘿” 了。


# 5.课后练习
# 有文件：0013_a1.txt
# 该文件的内容编码方式是 utf8。
# 该文件中记录了小区中人的姓名和年龄
# 请编写一个程序，将其中年龄大于50岁的找出来， 并且以这样的格式追加到该文件末尾中。
# 大于50岁的人有：xxx,xxx,xxx
# step1：读取文件内容
f = open('asset/0013_a1.txt', 'r', encoding='utf8')
fileContent = f.read()
f.close()
print(fileContent)

# step2：将文件内容转化成有效列表
personList = list(set(fileContent.splitlines()))
personList.remove('')
print(personList)

# step3：定义接收年龄大于50岁的列表
over50 = []

# step4：遍历原始列表，获取年龄大于50的人名
for personInfo in personList:
    infoList = personInfo.split(':')
    print(infoList)
    if len(infoList) > 0 and int(infoList[1].strip()) > 50:
        over50.append(infoList[0].strip())

print(over50)

# step5：将年龄大于50岁的人名列表转换成字符串
nameStr = ','.join(over50)
print(nameStr)

# step6：将字符串追加到 0013_a1.txt 文件的末尾
f = open('asset/0013_a1.txt', 'a', encoding='utf8')
f.write('大于50岁的人有：' + nameStr)
f.close()

# 练习优化
# 因为文件是utf8编码
with open('asset/0013_a1.txt', encoding='utf8') as f:
    lines = f.read().splitlines()

oldPeople = []
for line in lines:
    # 去掉行中的空格
    line = line.replace(' ', '')

    # 如果是空格，跳过
    if not line:
        continue

    name, age = line.split(':')
    if int(age) > 50:
        oldPeople.append(name)

oldPeopleStr = ','.join(oldPeople)
appendText = f'\n\n大于50岁的人有：{oldPeopleStr}'

with open('asset/0013_a1.txt', 'a', encoding='utf8') as f:
    f.write(appendText)
